"""Spatial Reference System

This module is just a simple wrapper of functionalities provided by pyproj.
"""
from typing import Union
import math
import pyproj

from .geom import Geom

VERTICAL_COLUMNS_NORTH = set("NPQRSTUVWX")
VERTICAL_COLUMNS_SOUTH = set("MLKJHGFEDC")


def utm_zone_to_epsg(utm_zone: int, is_north: bool = True) -> int:
    """Determine the EPSG code for a given UTM zone.

    Parameters
    ----------
    utm_zone : int
        UTM zone number, in the range of 1 - 60.
    is_north : bool, optional
        Whether it is in the northern hemisphere, by default True.

    Returns
    -------
    int
        EPSG number determined.

    Raises
    ------
    ValueError
        Raised when the given `utm_zone` is not a valid one.
    """
    if (utm_zone < 1) or (utm_zone > 60):
        raise ValueError("UTM zone number should be in the range of 1 to 60.")
    if is_north:
        epsg_no = 32600 + utm_zone
    else:
        epsg_no = 32700 + utm_zone
    return epsg_no


def coords_to_epsg_under_utm(lon: float, lat: float) -> int:
    """Get UTM zone EPSG code from a given pair of longitude and latitude."""
    if lat > 0:
        epsg_no = 32600
    else:
        epsg_no = 32700
    epsg_no += math.floor((180 + lon) / 6.0) + 1
    return epsg_no


def geom_to_epsg_under_utm(geom: Geom) -> int:
    """Get UTM zone EPSG code from a given geometry under WGS84 coordinate system"""
    lon = geom.centroid._shapely_geom.x
    lat = geom.centroid._shapely_geom.y
    return coords_to_epsg_under_utm(lon, lat)


def mgrs_tile_to_epsg(mgrs_tile: str) -> int:
    """Determine the EPSG number for a given MGRS 100-km square tile or Grid-Zone-Designator (GZD)

    Parameters
    ----------
    mgrs_tile : str
        MGRS tile id, eg. "T48RVV", or "48RVV";
        Or a MGRS GZD, eg. "T48R" or "48R".

    Returns
    -------
    int
        EPSG code determined.

    Raises
    ------
    ValueError
        Raised when the given `mgrs_tile` is not a valid one.
    """
    if len(mgrs_tile) == 6 and mgrs_tile.startswith("T"):
        utm_zone = int(mgrs_tile[1:3])
        vertical_col = mgrs_tile[3]
    elif len(mgrs_tile) == 5:
        utm_zone = int(mgrs_tile[:2])
        vertical_col = mgrs_tile[2]
    elif len(mgrs_tile) == 4 and mgrs_tile.startswith("T"):
        utm_zone = int(mgrs_tile[1:3])
        vertical_col = mgrs_tile[3]
    elif len(mgrs_tile) == 3:
        utm_zone = int(mgrs_tile[:2])
        vertical_col = mgrs_tile[2]

    if vertical_col in VERTICAL_COLUMNS_NORTH:
        is_north = True
    elif vertical_col in VERTICAL_COLUMNS_SOUTH:
        is_north = False
    else:
        raise ValueError(f"Unknown vertical_col {vertical_col}")

    return utm_zone_to_epsg(utm_zone, is_north=is_north)


def wkt_to_epsg(wkt_str: str, get_str: bool = True) -> Union[int, str]:
    """Get EPSG number or string from a WKT projection string"""
    epsg_no = pyproj.CRS(wkt_str).to_epsg()
    if epsg_no is None:
        raise ValueError("Cannot identify a EPSG code for the given projection")
    else:
        if get_str:
            return "EPSG:{}".format(epsg_no)
        else:
            return epsg_no


def epsg_to_wkt(epsg_str: str) -> str:
    """Get WKT projection string from an EPSG string"""
    return pyproj.CRS(epsg_str).to_wkt()
